/*
 * Copyright (c) 2009-2010 HIT Microelectronic Center
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * Authors: Gou Pengfei
 *
 * Date:    May. 2010
 *
 */
 
#include "cpu/edge/opn/opn.hh"
#include "cpu/edge/dist/impl.hh"

using namespace std;

template<class Impl>
OperandNetwork<Impl>::OperandNetwork(CPU *cpu_ptr, ExeUnit *exe_unit, DistributedEdgeCPUParams *params)
    : hopLatency(params->networkHopLatency),
    sendOccupancy(params->networkSendOccupancy),
    sendLatency(params->networkSendLatency),
    receiveOccupancy(params->networkReceiveOccupancy),
    receiveLatency(params->networkReceiveLatency),
    localInputFifoSize(params->networkLocalInputFifoSize),
    routerFifoSize(params->networkRouterFifoSize),
    width(params->networkWidth),
    cpu(cpu_ptr),
    exeUnit(exe_unit)
{
}

template<class Impl>
OperandNetwork<Impl>::~OperandNetwork()
{
    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
    
        if (opnFF[i]) {
            delete opnFF[i];
        }
        
        if (opnMX[i]) {
            delete opnMX[i];
        }
        
    }
}

template <class Impl>
std::string
OperandNetwork<Impl>::name() const
{
    return exeUnit->name() +  ".router";
}

template<class Impl>
void
OperandNetwork<Impl>::init(int _x, int _y, int _id) 
{
    x = _x;
    y = _y;
    id = _id;

    neighN = neighS = neighE = neighW = NULL;
    timeBufferNout = NULL;
    timeBufferNin = NULL;
    timeBufferSout = NULL;
    timeBufferSin = NULL;
    timeBufferEout = NULL;
    timeBufferEin = NULL;
    timeBufferWout = NULL;
    timeBufferWin = NULL;
    timeBufferLout = NULL;
    timeBufferLin = NULL;

    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
    
        for (int j = 0;j < TheISA::OPN_DIR_NUM - 1;j++) {
            muxValid[i][j] = false;
        }
        
        ffDestDirection[i] = TheISA::OPN_INV;
        muxSelectDirection[i] = TheISA::OPN_INV;

        std::stringstream *ss = new std::stringstream();
        
        if (i == TheISA::OPN_N) {
            *ss << "NORTH";
        } else if (i == TheISA::OPN_S) {
            *ss << "SOUTH";
        } else if (i == TheISA::OPN_E) {
            *ss << "EAST";
        } else if (i == TheISA::OPN_W) {
            *ss << "WEST";
        } else if (i == TheISA::OPN_L) {
            *ss << "LOCAL";
        }

        opnFF[i] = new OpnFifo<Impl>();
        opnFF[i]->init(ss->str());

        opnMX[i] = new OpnMux();

        if (i == TheISA::OPN_L) {
             opnMX[i]->init(ss->str(), localInputFifoSize);
        } else {
             opnMX[i]->init(ss->str(), routerFifoSize);
        }

        delete ss;
    }
}

template<class Impl>
typename OperandNetwork<Impl>::Dir_t 
OperandNetwork<Impl>::decodePacketInFifo(OpnFifo<Impl> *f) 
{
    
    if (f->empty()) {
        DPRINTF(EdgeOPN,"Nothing in fifo.\n");
        return TheISA::OPN_INV;
    }

    OpnPktPtr p = f->peek();

    DPRINTF(EdgeOPN,"Packet: Src[X:%i][Y:%i] ----> Dest[X:%i][Y:%i]."
        " This node is [X:%i][Y:%i]\n",
        p->info->srcX,p->info->srcY,
        p->info->destX,p->info->destY,x,y);

    // Y first routing
    if ( p->info->destY > y ) { 
        return TheISA::OPN_S;
    }

    if ( p->info->destY < y ) { 
        return TheISA::OPN_N;
    }

    if ( p->info->destX > x ) { 
        return TheISA::OPN_E;
    }

    if ( p->info->destX < x ) { 
        return TheISA::OPN_W;
    }

    return TheISA::OPN_L;

}

template<class Impl>
typename OperandNetwork<Impl>::Dir_t 
OperandNetwork<Impl>::decodePacketReceived(OpnPktPtr &pkt) 
{
    panic("Unimplemented func: decodePacketReceived.\n");
}

template<class Impl>
void 
OperandNetwork<Impl>::updateMuxCredit()
{

    DPRINTF(EdgeOPN,"Updating Mux credit.\n");

    int nin_credits = opnNin->credits;
    int sin_credits = opnSin->credits;
    int ein_credits = opnEin->credits;
    int win_credits = opnWin->credits;
    int lin_credits = opnLin->credits;

    if (nin_credits > 0) {

        opnMX[TheISA::OPN_N]->credits += nin_credits;

        DPRINTF(EdgeOPN,"Updating credits of NORTH."
            "Credits = %i.\n", opnMX[TheISA::OPN_N]->credits);

        assert(opnMX[TheISA::OPN_N]->credits <= routerFifoSize);
    }
    if (sin_credits > 0) {

        opnMX[TheISA::OPN_S]->credits += sin_credits;

        DPRINTF(EdgeOPN,"Updating credits of SOUTH."
            "Credits = %i.\n",opnMX[TheISA::OPN_S]->credits);

        assert(opnMX[TheISA::OPN_S]->credits <= routerFifoSize);
    }
    if (ein_credits > 0) {

        opnMX[TheISA::OPN_E]->credits += ein_credits;

        DPRINTF(EdgeOPN,"Updating credits of EAST."
            "Credits = %i.\n",opnMX[TheISA::OPN_E]->credits);

        assert(opnMX[TheISA::OPN_E]->credits <= routerFifoSize);
    }
    if (win_credits > 0) {

        opnMX[TheISA::OPN_W]->credits += win_credits;

        DPRINTF(EdgeOPN,"Updating credits of WEST."
            "Credits = %i.\n",opnMX[TheISA::OPN_W]->credits);

        assert(opnMX[TheISA::OPN_W]->credits <= routerFifoSize);
    }
    if (lin_credits > 0) {

        opnMX[TheISA::OPN_L]->credits += lin_credits;

        DPRINTF(EdgeOPN,"Updating credits of LOCAL."
            "Credits = %i.\n",opnMX[TheISA::OPN_L]->credits);

        assert(opnMX[TheISA::OPN_L]->credits <= localInputFifoSize);
    }

}

template<class Impl>
typename OperandNetwork<Impl>::Dir_t
OperandNetwork<Impl>::selectFifo(Dir_t src_dir)
{
    DPRINTF(EdgeOPN,"Select fifo of %i.\n",src_dir);
    
    Dir_t dest_dir = decodePacketInFifo(opnFF[src_dir]);

    if (dest_dir == TheISA::OPN_INV) {
        DPRINTF(EdgeOPN,"Fifo[%i] gets no packet.\n",src_dir);
        return TheISA::OPN_INV;
    }

    DPRINTF(EdgeOPN,"Fifo[%i] gets packet to %i.\n",src_dir,dest_dir);
    
    if (opnMX[dest_dir]->credits == 0) {
        DPRINTF(EdgeOPN,"Not enough credits in MX[%i].\n",dest_dir);
        return TheISA::OPN_INV;
    }

    DPRINTF(EdgeOPN,"%i credits in MX[%i].\n",
        opnMX[dest_dir]->credits,dest_dir);
    
    return dest_dir;
}

template<class Impl>
void 
OperandNetwork<Impl>::selectFifo() 
{

    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
        ffDestDirection[i] = decodePacketInFifo(opnFF[i]);
    }

    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
        muxValid [i][TheISA::OPN_N] = 
          ((ffDestDirection[TheISA::OPN_N] == i) ? true : false);
          
        muxValid [i][TheISA::OPN_S] = 
          ((ffDestDirection[TheISA::OPN_S] == i) ? true : false );
          
        muxValid [i][TheISA::OPN_E] = 
          ((ffDestDirection[TheISA::OPN_E] == i) ? true : false );
          
        muxValid [i][TheISA::OPN_W] = 
          ((ffDestDirection[TheISA::OPN_W] == i) ? true : false );
          
        muxValid [i][TheISA::OPN_L] = 
          ((ffDestDirection[TheISA::OPN_L] == i) ? true : false );
    }

    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
        if (noFfValid (muxValid[i])) {
            muxSelectDirection[i] = TheISA::OPN_INV;
            
            DPRINTF(EdgeOPN,"Set source direction of mux #%i to INVALID.\n", i);
        } else {
            muxSelectDirection[i] = opnMX[i]->select(muxValid[i]);

            if (opnMX[i]->credits == 0) {
                DPRINTF(EdgeOPN,"CAUTION: Output direction "
                    "%i has no credits.\n",i);
            }
            
            DPRINTF(EdgeOPN,"Set source direction of MXSelect[%i] to %i."
                "Credits of MX[%i] = %i.\n", i, 
                muxSelectDirection[i],
                i,opnMX[i]->credits);
        }
    }
}

template<class Impl>
void 
OperandNetwork<Impl>::clearOutWires() 
{
    panic("Unimplemented func: clearOutWires");
}

template<class Impl>
int
OperandNetwork<Impl>::setOutWires() 
{
    Wire dest_wire;
    Wire src_wire;

    // Count number of outputs for each direction, so
    // that we can stay within the bandwidth limit.
    int num_packets[5] = {0,0,0,0,0};

    for (int w = 0; w < width; w++) {
        selectFifo();
        // i represents the destination direction of the routed packet
        for (int i = 0; i < TheISA::OPN_DIR_NUM - 1; i++) {     
            if (i == TheISA::OPN_N) dest_wire = opnNout;
            else if (i == TheISA::OPN_S) dest_wire = opnSout;
            else if (i == TheISA::OPN_E) dest_wire = opnEout;
            else if (i == TheISA::OPN_W) dest_wire = opnWout;
            else if (i == TheISA::OPN_L) dest_wire = opnLout; 
            else {
                dest_wire = opnLout;
                panic("Unrecogonized direction.\n");
            }

            if (muxSelectDirection[i] != TheISA::OPN_INV) {
                // j represents the source direction of the routed packet
                for (int j = 0; j < TheISA::OPN_DIR_NUM - 1; j++) {  
                    if (muxSelectDirection[i] == j) {
                    
                        if (j == TheISA::OPN_N) src_wire = opnNout;
                        else if (j == TheISA::OPN_S) src_wire = opnSout;
                        else if (j == TheISA::OPN_E) src_wire = opnEout;
                        else if (j == TheISA::OPN_W) src_wire = opnWout;
                        else if (j == TheISA::OPN_L) src_wire = opnLout;
                        else {
                            src_wire = opnLout;
                            panic("Unrecogonized direction.\n");
                        }
                        
                        if (opnMX[i]->credits != 0) {
                            if (num_packets[i] >= width) {
                                DPRINTF(EdgeOPN,"Reach the bandwidth limit"
                                    " for output direction %i.\n",i);
                                break;
                            }

                            OpnPktPtr pkt_to_send = opnFF[j]->peek();
                            
                            if (!pkt_to_send->valid || pkt_to_send->squashed) {

                                DPRINTF(EdgeOPN,"Invalid packet[src id:%i][src x:%i][src y:%i]"
                                        " to send, the inst responsible for"
                                        "this packet may have been squashed or"
                                        " block completed.\n",
                                        pkt_to_send->info->srcID,
                                        pkt_to_send->info->srcX,
                                        pkt_to_send->info->srcY);
                                                          
                                opnFF[j]->pop();

                                // Make an acknowledgement to src node.
                                src_wire->credits++;
                                DPRINTF(EdgeOPN,"Make an acknowledgement to %i.\n",j);
                                
                                continue;
                            }

                            DPRINTF(EdgeOPN,"Start sending a packet from node[%i]"
                                " through direction:%i\n",id,i);
                            
                            opnMX[i]->credits--;
                  
                            assert(opnMX[i]->credits >= 0);     

                            DPRINTF(EdgeOPN,"Decrease credits of MX[%i]. "
                                "Credits = %i.\n",i,opnMX[i]->credits);             
                                
                            pkt_to_send->info->senderID = id;
                            pkt_to_send->info->senderX = x;
                            pkt_to_send->info->senderY = y;    

                            dest_wire->packets[num_packets[i]] = opnFF[j]->peek();
                            dest_wire->size++;

                            assert(dest_wire->size <= Impl::MaxOpnWidth);

                            opnFF[j]->pop();

                            // Make an acknowledge to the src node of 
                            // the sent pkt.
                            src_wire->credits++;

                            DPRINTF(EdgeOPN,"Make an acknowledgement to %i.\n",j);

                            num_packets[i]++;

                        } else {
                            DPRINTF(EdgeOPN,"Output direction %i "
                                "has no credits.\n",i);
                        }
                    }
                }
            }
        }
    }

    // Return the number of packets processed.
    return num_packets[0] + num_packets[1] +
           num_packets[2] + num_packets[3] +
           num_packets[4];
}

template<class Impl>
void 
OperandNetwork<Impl>::squash(TheISA::BlockID block_id) 
{

    Wire in_wire;
    int fcnt[5] = {0, 0, 0, 0, 0};

    DPRINTF(EdgeOPN,"Squashing packets in OPN.\n");

    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
        opnFF[i]->squash(block_id);
        fcnt[i] = opnFF[i]->getSquashCnt();
    }

    if (fcnt[TheISA::OPN_N] > 0) {
        neighN->router.opnMX[TheISA::OPN_S]->credits += fcnt[TheISA::OPN_N]; 

        assert(neighN->router.opnMX[TheISA::OPN_S]->credits <= routerFifoSize);
    }
    
    if (fcnt[TheISA::OPN_S] > 0) {
        neighS->router.opnMX[TheISA::OPN_N]->credits += fcnt[TheISA::OPN_S]; 
        assert(neighS->router.opnMX[TheISA::OPN_N]->credits <= routerFifoSize);
    }
    
    if (fcnt[TheISA::OPN_E] > 0) {
        neighE->router.opnMX[TheISA::OPN_W]->credits += fcnt[TheISA::OPN_E]; 
       assert(neighE->router.opnMX[TheISA::OPN_W]->credits <= routerFifoSize);
    }
    
    if (fcnt[TheISA::OPN_W] > 0) {
        neighW->router.opnMX[TheISA::OPN_E]->credits += fcnt[TheISA::OPN_W]; 
       assert(neighW->router.opnMX[TheISA::OPN_E]->credits <= routerFifoSize);
    }
    
    if (fcnt[TheISA::OPN_L] > 0) {
        exeUnit->opnInterface.credits += fcnt[TheISA::OPN_L]; 
       assert(opnMX[TheISA::OPN_L]->credits <= localInputFifoSize);
    }
      
    for (int i = 0;i < TheISA::OPN_DIR_NUM - 1;i++) {
        opnFF[i]->initSquashCnt();
    }

}

template<class Impl>
void 
OperandNetwork<Impl>::squashWires(TheISA::BlockID block_id, int *fcnt) 
{
    panic("Unimplemented func: squashWIres.\n");
}

template<class Impl>
void 
OperandNetwork<Impl>::pushToFifo()
{
    for (int i=0;i < opnNin->size;i++) {
        DPRINTF(EdgeOPN,"Pushing to NORTH fifo.\n");
        
        opnFF[TheISA::OPN_N]->push(opnNin->packets[i]);
    }
    
    for (int i=0;i < opnSin->size;i++) {
        DPRINTF(EdgeOPN,"Pushing to SOUTH fifo.\n");
        
        opnFF[TheISA::OPN_S]->push(opnSin->packets[i]);
    }
    
    for (int i=0;i < opnEin->size;i++) {
        DPRINTF(EdgeOPN,"Pushing to EAST fifo.\n");
        
        opnFF[TheISA::OPN_E]->push(opnEin->packets[i]);
    }

    for (int i=0;i < opnWin->size;i++) {
        DPRINTF(EdgeOPN,"Pushing to WEST fifo.\n");
        
        opnFF[TheISA::OPN_W]->push(opnWin->packets[i]);
    }

    for (int i=0;i < opnLin->size;i++) {
        DPRINTF(EdgeOPN,"Pushing to LOCAL fifo.\n");
        
        opnFF[TheISA::OPN_L]->push(opnLin->packets[i]);
    }
}

template<class Impl>
int
OperandNetwork<Impl>::promote() 
{
    DPRINTF(EdgeOPN,"Promoting OPN router #%i.\n", id);

    pushToFifo();

    int promoted_packets = setOutWires();

    return promoted_packets;
}

template<class Impl>
void
OperandNetwork<Impl>::receive(OpnPktPtr &pkt)
{
    panic("Unimplemented func: receive.\n");
}

template<class Impl>
void
OperandNetwork<Impl>::localInject(OpnPktPtr &pkt)
{
    panic("Unimplemented function: loacalInject.\n");
}

template<class Impl>
void
OperandNetwork<Impl>::send(ExeUnit *dest_node,OpnPktPtr &pkt_to_send)
{
    panic("Unimplemented func: send.\n");
}

template<class Impl>
void 
OperandNetwork<Impl>::printIncoming() 
{
    panic("Unimplemented func: printIncoming.\n");
}

template<class Impl>
void
OperandNetwork<Impl>::setNeighbor(Neighbor * neigh,Dir_t direction)
{
    assert(neigh);
    
    switch(direction) {
        case TheISA::OPN_N: neighN = neigh;break;
        case TheISA::OPN_S: neighS = neigh;break;
        case TheISA::OPN_E: neighE = neigh;break;
        case TheISA::OPN_W: neighW = neigh;break;
        default:
            panic("Not supported direction when set neighbors.\n");
    }

}

template<class Impl>
void
OperandNetwork<Impl>::setTimeBuffer(TimeBuffer<OpnTimeStruct> *buff_ptr,
        Dir_t direction,bool in)
{
    assert(buff_ptr);

    switch(direction) {
        case TheISA::OPN_N: {
            if (in) {
                // Set North timebuffer for input.
                timeBufferNin = buff_ptr;
                opnNin = timeBufferNin->getWire(-hopLatency);
            } else {
                // Set North timebuffer for output.
                timeBufferNout = buff_ptr;
                opnNout = timeBufferNout->getWire(0);
            }
            
            break;
        }
        case TheISA::OPN_S: {
            if (in) {
                // Set South timebuffer for input.
                timeBufferSin = buff_ptr;
                opnSin = timeBufferSin->getWire(-hopLatency);
            } else {
                // Set South timebuffer for output.
                timeBufferSout = buff_ptr;
                opnSout = timeBufferSout->getWire(0);
            }
            
            break;
        }
        case TheISA::OPN_E: {
            if (in) {
                // Set East timebuffer for input.
                timeBufferEin = buff_ptr;
                opnEin = timeBufferEin->getWire(-hopLatency);
            } else {
                // Set East timebuffer for output.
                timeBufferEout = buff_ptr;
                opnEout = timeBufferEout->getWire(0);
            }
            
            break;
        }
        case TheISA::OPN_W: {
            if (in) {
                // Set West timebuffer for input.
                timeBufferWin = buff_ptr;
                opnWin = timeBufferWin->getWire(-hopLatency);
            } else {
                // Set West timebuffer for output.
                timeBufferWout = buff_ptr;
                opnWout = timeBufferWout->getWire(0);
            }
            
            break;
        }
        case TheISA::OPN_L: {
            if (in) {
                // Set Local timebuffer for input.
                timeBufferLin = buff_ptr;
                // Should sendLatency be set here? I'm not sure.
                opnLin = timeBufferLin->getWire(-sendLatency);
            } else {
                // Set Local timebuffer for output.
                timeBufferLout = buff_ptr;
                opnLout = timeBufferLout->getWire(0);
            }
            
            break;
        }
        default:
            panic("Not supported direction when set timebuffers.\n");
    }
    
}

template class OperandNetwork<DistEdgeCPUImpl>;

